package de.tu_darmstadt.informatik.tk.lernsystem.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.methods.GetMethod;
import org.htmlparser.Parser;
import org.htmlparser.Tag;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.tags.BaseHrefTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.visitors.NodeVisitor;

import de.tu_darmstadt.informatik.tk.lernsystem.util.Config;
import de.tu_darmstadt.informatik.tk.lernsystem.util.XpathFilter;

/**
 * This class acts as a proxy and highlights Xpaths.
 * 
 * Two parameters can be provided:
 * - url: URL to load
 * - xpath: XPath to highlight
 * 
 * Highlighting can be configured in settings.properties file.
 */
public class ProxyServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String url = request.getParameter("url");
		String xpath = request.getParameter("xpath");
		
		HttpClient client = new HttpClient();
		HttpMethod method = new GetMethod(url);
		int statusCode = client.executeMethod(method);
		
		URI uri = method.getURI();
		String documentUrl = method.getURI().toString();
		String hostUrl = uri.getScheme() + "://" + uri.getHost() + "/";
		String baseUrl = uri.getScheme() + "://" + uri.getHost() + uri.getEscapedCurrentHierPath();
		
		if (statusCode != HttpStatus.SC_OK) {
			System.err.println("Method failed: " + method.getStatusLine());
		}
		String content = method.getResponseBodyAsString();
		//content = turnRemoteRequestsToProxyRequests(content, documentUrl, hostUrl, baseUrl);
		content = highlightXpath(content, xpath, baseUrl);
		content = turnLocalRequestsToRemoteRequests(content, documentUrl, hostUrl, baseUrl);
		
		response.setContentType(method.getResponseHeader("Content-type").getValue());
		PrintWriter out = response.getWriter();
		out.print(content);
		out.flush();
		out.close();
		
		method.releaseConnection();
	}
	
	/**
	 * Does the actual highlighting.
	 * 
	 * @param content HTML content
	 * @param xpath XPath to highlight
	 * @param baseHref Base HREF the page can load additional resources from.
	 * @return Highlighted HTML code.
	 */
	private String highlightXpath(String content, String xpath, String baseHref) {
		if (xpath.isEmpty()) {
			return content;
		}
		try {
			Parser parser = new Parser(content);
			NodeList list = parser.parse(null);
			XpathFilter filter = new XpathFilter(list, xpath);
			filter.parse();
			List<Tag> result = filter.getResult();
			for (Tag tag : result) {
				String style = (tag.getAttribute("style") != null) ? tag.getAttribute("style") : "";
				tag.setAttribute("style", style+";"+Config.getInstance().getString("proxy.hightlight"));
			}
			BaseHrefTag tag = new BaseHrefTag();
			tag.setBaseUrl(baseHref);
			NodeList headTag = list.extractAllNodesThatMatch(new TagNameFilter("head"), true);
			if (headTag.size() > 0) {
				((Tag)headTag.elementAt(0)).getChildren().add(tag);
			}
			return list.toHtml();
		} catch (Exception e) {
			e.printStackTrace();
			return content;
		}
	}
	
	/**
	 * Alters "some" resources so that requests that would be
	 * sent to the proxy servlet are handled directly by the
	 * originating site.
	 * 
	 * @param content HTML Code
	 * @param document Source URL
	 * @param host Source host
	 * @param baseUrl Base URL
	 * @return Altered HTML Code
	 */
	private String turnLocalRequestsToRemoteRequests(String content, final String document, final String host, final String baseUrl) {
		try {
			Parser parser = new Parser(content);
			NodeList list = parser.parse(null);
			list.visitAllNodesWith(new NodeVisitor() {
				@Override
		        public void visitTag(Tag tag) {
					if ("a".equals(tag.getTagName().toLowerCase())) {
						if (tag.getAttribute("href") != null) {
							tag.setAttribute("href", makeImgUrlToProxyUrl(tag.getAttribute("href"), document, host, baseUrl));
						}
					} else if ("img".equals(tag.getTagName().toLowerCase())) {
						if (tag.getAttribute("src") != null) {
							tag.setAttribute("src", makeImgUrlToProxyUrl(tag.getAttribute("src"), document, host, baseUrl));
						}
					} else if ("script".equals(tag.getTagName().toLowerCase())) {
						if (tag.getAttribute("src") != null) {
							tag.setAttribute("src", makeImgUrlToProxyUrl(tag.getAttribute("src"), document, host, baseUrl));
						}
					} else if ("link".equals(tag.getTagName().toLowerCase())) {
						if (tag.getAttribute("href") != null) {
							tag.setAttribute("href", makeImgUrlToProxyUrl(tag.getAttribute("href"), document, host, baseUrl));
						}
					}
				}
			});
			return list.toHtml();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return content;
	}
	
	/**
	 * Alters some resources so that requests that would be
	 * sent to the remote site in a way that they are instead
	 * sent to the proxy servelet (for further processing).
	 * 
	 * @param content HTML Code
	 * @param document Source URL
	 * @param host Source Host
	 * @param baseUrl Base URL
	 * @return Altered HTML Code
	 */
	@SuppressWarnings("unused")
	private String turnRemoteRequestsToProxyRequests(String content, final String document, final String host, final String baseUrl) {
		try {
			Parser parser = new Parser(content);
			NodeList list = parser.parse(null);
			list.visitAllNodesWith(new NodeVisitor() {
				@Override
		        public void visitTag(Tag tag) {
					if ("a".equals(tag.getTagName().toLowerCase())) {
						if (tag.getAttribute("href") != null) {
							tag.setAttribute("href", makeLinkUrlToProxyUrl(tag.getAttribute("href"), document, host, baseUrl));
							tag.removeAttribute("target");
						}
					} else if ("img".equals(tag.getTagName().toLowerCase())) {
						if (tag.getAttribute("src") != null) {
							tag.setAttribute("src", makeImgUrlToProxyUrl(tag.getAttribute("src"), document, host, baseUrl));
						}
					} else if ("script".equals(tag.getTagName().toLowerCase())) {
						if (tag.getAttribute("src") != null) {
							tag.setAttribute("src", makeImgUrlToProxyUrl(tag.getAttribute("src"), document, host, baseUrl));
						}
					} else if ("link".equals(tag.getTagName().toLowerCase())) {
						if (tag.getAttribute("href") != null) {
							tag.setAttribute("href", makeImgUrlToProxyUrl(tag.getAttribute("href"), document, host, baseUrl));
						}
					}
				}
			});
			return list.toHtml();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return content;
	}
	
	/**
	 * Encodes an URL (like PHP's urlencode() function)
	 * 
	 * @param url URL to encode
	 * @return Encoded URL
	 */
	private String getEncodedUrl(String url) {
		try {
			return URLEncoder.encode(url, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			return url;
		}
	}
	
	/**
	 * Changes a link URL to that it points to the proxy.
	 * 
	 * @param url
	 * @param document
	 * @param host
	 * @param baseUrl
	 * @return
	 */
	private String makeLinkUrlToProxyUrl(String url, String document, String host, String baseUrl) {
		if (!baseUrl.endsWith("/")) {
			baseUrl += "/";
		}
		if (url == null) {
			return null;
		}
		if (url.toLowerCase().startsWith("http://")) {
			return "./proxy?url="+getEncodedUrl(url);
		}
		if (url.toLowerCase().startsWith("https://")) {
			return "./proxy?url="+getEncodedUrl(url);
		}
		if (url.toLowerCase().startsWith("#")) {
			return "./proxy?url="+document+getEncodedUrl(url);
		}
		if (url.toLowerCase().startsWith("/")) {
			return "./proxy?url="+host+getEncodedUrl(url);
		}
		String newUrl = "./proxy?url="+baseUrl + getEncodedUrl(url);
		return newUrl;
	}
	
	/**
	 * Changes an image URL so that it points to the proxy.
	 * 
	 * @param url
	 * @param document
	 * @param host
	 * @param baseUrl
	 * @return
	 */
	private String makeImgUrlToProxyUrl(String url, String document, String host, String baseUrl) {
		if (!baseUrl.endsWith("/")) {
			baseUrl += "/";
		}
		if (host.endsWith("/")) {
			host = host.substring(0, host.length()-1);
		}
		if (url == null) {
			return null;
		}
		if (url.toLowerCase().startsWith("http://")) {
			return url;
		}
		if (url.toLowerCase().startsWith("https://")) {
			return url;
		}
		if (url.toLowerCase().startsWith("/")) {
			return host+url;
		}
		String newUrl = baseUrl + url;
		return newUrl;
	}
}
